package main

import (
	"bytes"
	"context"
	"crypto/tls"
	"fmt"
	"github.com/go-redis/redis/v8"
	"io/ioutil"
	"net/http"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

type InfoStruct struct {
	Method    string            //用户的
	UrlPath   []string          //url路径
	Token     string            //token
	Cookie    string            //cookie
	Params    map[string]string //url后面的参数
	Body      string            //请求内容
	TodayStr  string            //当天字符串,20230215
	Service   string            //服务名称	golang-zk4g代表正式版  golang-debug代表公测版
	IsGzip    bool              //返回值是否采用gzip
	IP        map[string]string //访问的真实IP
	Project   string            //项目ID
	UserInfo  map[string]string //用户当前信息
	UserLogin bool              //用户是否登录
}
type ReturnMsg struct {
	ErrorCode int         `json:"errorCode"`
	Error     string      `json:"error"`
	Data      interface{} `json:"data"`
}

var redisDB *redis.Client //redis.Client
var ctx = context.Background()

var RuiningCarbonChan chan RuiningDataStruct

/*
Index
比如  /接口名称/项目ID/参数1/参数2/参数3/参数4
*/
func Index2(w http.ResponseWriter, r *http.Request) {
	//1. 处理返回参数

	//2. 支持跨域

	//3. 处理url参数
	AllPath, UrlParams, _ := ParseUrlNoCheck(r.RequestURI)

	if len(AllPath) < 6 && len(AllPath[0]) < 8 {
		ErrorJson(w, -1, "路径不合法", InfoStruct{})
		return
	}
	CurProject, _ := redisDB.HGet(ctx, "allproject", AllPath[1]).Result()
	if CurProject == "" {
		ErrorJson(w, -1, "参数错误", InfoStruct{})
		return
	}

	//4. 处理是否对返回值进行gzip压缩
	IsGzip := false
	AcceptEncoding := r.Header.Get("Accept-Encoding")
	if strings.Replace(AcceptEncoding, "gzip", "", -1) != AcceptEncoding {
		IsGzip = true
	}

	//5. 读取body
	buf := new(bytes.Buffer)
	buf.ReadFrom(r.Body)
	bodyStr := buf.String()

	//6. 读取cookie
	cookie := r.Header.Get("usercookie")

	//7. 获取真实IP
	IP10, _ := GetIP(r)
	IP11 := map[string]string{}
	for _, v := range IP10 {
		v = strings.Replace(v, " ", "", -1)
		va := strings.Split(v, ",")
		for _, v2 := range va {
			if v2 != "" {
				IP11[v2] = "1"
			}

		}
	}
	//8. 构建参数
	Info := InfoStruct{
		Method:   r.Method,
		UrlPath:  AllPath,
		Token:    "",
		Cookie:   cookie,
		Params:   UrlParams,
		Body:     bodyStr,
		Service:  "",
		IsGzip:   IsGzip,
		IP:       IP11,
		Project:  CurProject,
		UserInfo: map[string]string{},
		TodayStr: time.Now().Format("20060102"),
	}

	LoginCheck, _ := redisDB.HGet(ctx, CurProject+":sysconfig", "LoginCheck").Result()
	if LoginCheck == "1" && AllPath[0] != "User_login" {
		//1. 检查cookie
		if cookie == "" {
			ErrorJson(w, -10, "请先登录", InfoStruct{})
			return
		}

		//2. 检查cookie是否可以解密
		logininfo, isSuc := JSAesDecrypt2(cookie)
		if isSuc == false {
			ErrorJson(w, -10, "请先登录", InfoStruct{})
			return
		}
		//3. 检查cookie是否合法
		logins := strings.SplitN(logininfo, ":", 4)
		if len(logins) != 4 {
			ErrorJson(w, -10, "请先登录", InfoStruct{})
			return
		}
		//4. 检查登录IP是否正确
		ips := strings.Split(logins[3], "&")
		isIPSuccess := false
		for _, v := range ips {
			if IP11[v] != "" {
				isIPSuccess = true
				break
			}
		}
		if isIPSuccess == false {
			ErrorJson(w, -10, "请先登录", InfoStruct{})
			return
		}
		//5. 检查密码是否符合
		isLoginSuccess, UserInfo := UserGet_login(logins[0], logins[1], CurProject, Str2Int64(logins[2]))
		if isLoginSuccess == false {
			ErrorJson(w, -10, "请先登录", InfoStruct{})
			return
		}
		//6. 检查接口权限
		Level := UserInfo["Level"]
		if Level == "1" && (AllPath[0][0:4] == "Set_" || AllPath[2] == "useradmin" || AllPath[2] == "sysconfig") {
			ErrorJson(w, -10, "权限不足", InfoStruct{})
			return
		}
		if Level == "2" && (AllPath[2] == "useradmin" || AllPath[2] == "sysconfig") {
			ErrorJson(w, -10, "权限不足", InfoStruct{})
			return
		}
		Info.UserInfo = UserInfo
		delete(Info.UserInfo, "Psd")
	}

	//9. 反射方法，并执行
	pp := reflect.ValueOf(Imp) // 取得struct变量的指针
	MethodName := AllPath[0]
	f := pp.MethodByName(MethodName)
	if !f.IsValid() {
		ErrorJson(w, -2, "接口不存在", Info)
		return
	}
	paramList := []reflect.Value{reflect.ValueOf(w), reflect.ValueOf(r), reflect.ValueOf(Info)}
	f.Call(paramList)
	return
}

func CurMain() {
	RedisIP := "10.0.240.12:6379"
	if runtime.GOOS == "windows" {
		RedisIP = "127.0.0.1:35002"
	}
	RuiningCarbonChan = make(chan RuiningDataStruct, 10000)
	go RuiningCarbonThread()
	redisDB = redis.NewClient(&redis.Options{
		Addr:     RedisIP, //
		Password: "",      // no password set
		DB:       0,       // use default DB
		//连接池容量及闲置连接数量
		PoolSize:     15, // 连接池最大socket连接数，默认为4倍CPU数， 4 * runtime.NumCPU
		MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接，并长期维持idle状态的连接数不少于指定数量；。

		//超时
		DialTimeout:  5 * time.Second, //连接建立超时时间，默认5秒。
		ReadTimeout:  3 * time.Second, //读超时，默认3秒， -1表示取消读超时
		WriteTimeout: 3 * time.Second, //写超时，默认等于读超时
		PoolTimeout:  4 * time.Second, //当所有连接都处在繁忙状态时，客户端等待可用连接的最大等待时长，默认为读超时+1秒。

		//闲置连接检查包括IdleTimeout，MaxConnAge
		IdleCheckFrequency: 60 * time.Second, //闲置连接检查的周期，默认为1分钟，-1表示不做周期性检查，只在客户端获取连接时对闲置连接进行处理。
		IdleTimeout:        5 * time.Minute,  //闲置超时，默认5分钟，-1表示取消闲置超时检查
		MaxConnAge:         0 * time.Second,  //连接存活时长，从创建开始计时，超过指定时长则关闭连接，默认为0，即不关闭存活时长较长的连接

		//命令执行失败时的重试策略
		MaxRetries:      0,                      // 命令执行失败时，最多重试多少次，默认为0即不重试
		MinRetryBackoff: 8 * time.Millisecond,   //每次计算重试间隔时间的下限，默认8毫秒，-1表示取消间隔
		MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限，默认512毫秒，-1表示取消间隔

	})
}

type DailiStructData struct {
	StatusCode    int
	ContentType   string
	ContentLength string
	Data          []byte
	CreateTime    int64
}
type DailiStruct struct {
	Lock sync.RWMutex
	Data map[string]DailiStructData
}

var Daili DailiStruct
var httpClient *http.Client

var FindData = []string{"html", "js", "ts", "css", "png", "jpg", "ico"}

func MyHTTPResponse(Method string, Url string, Data string) (int, []byte, bool, *http.Response) {
	client := httpClient //Timeout: time.Millisecond * 500000
	req, err := http.NewRequest(Method, Url, strings.NewReader(Data))
	if err != nil {
		// handle error
		Log("myhttp1", err)
		return 0, nil, false, nil
	}

	req.Header.Set("user-agent", "n47d9ozt.sdtanpuhui.cn")
	resp, err1 := client.Do(req)
	if err1 != nil {
		Log("myhttp2", err1)
		return 0, nil, false, nil
	}
	defer resp.Body.Close()

	str, err2 := ioutil.ReadAll(resp.Body)
	if err2 != nil {
		Log("myhttp3", err2)
		return 1, nil, false, resp
	}
	return 1, str, true, resp
}

/*
Index
总接口，执行 /主接口名称/主接口附加数据/副接口名称/副接口附加数据/用户加密数字ID或微信ID  格式的接口
比如  /api/user/null/login/null/wxaa01testenvid99   ，会自动调用zend_user.go 里面的 UserLogin接口
如果新写了一个接口，请记得在zend_ainterface.go中添加该接口
*/
func Index(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
	w.Header().Set("Access-Control-Allow-Headers", "*")
	w.Header().Set("Access-Control-Allow-Origin", "*")
	if r.Method == "OPTIONS" {
		fmt.Fprint(w, "")
		return
	}
	if len(r.RequestURI) > 13 && r.RequestURI[0:13] == "/sdci_ruining" {
		r.RequestURI = strings.Replace(r.RequestURI, "/sdci_ruining", "", 1)
		Index2(w, r)
		return
	}
	if strings.Contains(r.RequestURI, "816545dc96e5eea58f4c8117cfc5c231") == true {
		fmt.Fprint(w, "0f6b515c07fcf1e3b05d194878519c770f6a5f8d")
		return
	}
	if r.RequestURI == "/cleardailitemp" || r.RequestURI == "/gaodex/cleardailitemp" {
		Daili.Lock.Lock()
		Daili.Data = map[string]DailiStructData{}
		Daili.Lock.Unlock()
		fmt.Fprint(w, "代理缓存清除成功，您可以刷新业务页页面，就可以看到最新的效果")
		return
	}
	fmt.Println(r.RequestURI)
	if r.RequestURI == "/" {
		r.RequestURI = "/gaodex/https_cache.gaode.com/activity/partner/2023ShanDongCarbon/index.html?v=20240925&adcode=370000&token="
	}
	if strings.Contains(r.RequestURI, "/gaodex") == false {
		r.RequestURI = "/gaodex/https_cache.gaode.com/activity/partner/2023ShanDongCarbon" + r.RequestURI
	}
	//fmt.Println("after", r.RequestURI)
	mainName := strings.Replace(r.RequestURI, "/gaodex/", "", 1)
	mainName = strings.Replace(mainName, ":/", "://", 1)
	mainName = strings.Replace(mainName, "_", "://", 1)

	FileTpe__ := strings.Split(strings.Split(mainName, "?")[0], ".")
	FileType := strings.ToLower(FileTpe__[len(FileTpe__)-1])
	nocache := true
	if FileType == "css" || FileType == "js" || FileType == "png" || FileType == "jpg" || FileType == "jpeg" || FileType == "mp4" || FileType == "avi" {
		nocache = false
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(r.Body)
	bodyStr := buf.String()

	kindname := strings.Split(strings.ToLower(strings.Split(mainName, "?")[0]), ".")
	mainNameWitoutWenh := kindname[len(kindname)-1]

	find := false

	if strings.ToLower(r.Method) == "get" && nocache == false {

		for _, v := range FindData {
			if mainNameWitoutWenh == v {
				find = true
				break
			}
		}
		if find == true {
			Daili.Lock.RLock()
			res, ok := Daili.Data[mainName]

			if ok == true {
				if res.CreateTime+86400*100000 > time.Now().Unix() {

					w.Header().Set("Content-Type", res.ContentType)
					w.Header().Set("Content-Length", res.ContentLength)
					w.WriteHeader(res.StatusCode)
					w.Write(res.Data)
					Daili.Lock.RUnlock()
					return
				}

			}
			Daili.Lock.RUnlock()

		}
	}

	_, text, error01, res := MyHTTPResponse(r.Method, mainName, bodyStr)
	Log("未命中缓存", mainName)
	if error01 == false {
		w.WriteHeader(404)
		w.Header().Set("Content-Length", "0")
		fmt.Fprint(w, "")
		return
	}
	ContentType := res.Header.Get("Content-Type")
	ContentLength := res.Header.Get("Content-Length")
	if ContentLength == "" {
		ContentLength = strconv.Itoa(len(text))
	}
	w.Header().Set("Content-Type", ContentType)
	//w.Header().Set("Content-Encoding", res.Header.Get("Content-Encoding"))
	//w.Header().Set("Content-Md5", res.Header.Get("Content-Md5"))
	//w.Header().Set("Eagleid", res.Header.Get("Eagleid"))
	w.Header().Set("Content-Length", ContentLength)
	w.WriteHeader(res.StatusCode)
	w.Write(text)

	if strings.ToLower(r.Method) == "get" && res.StatusCode == 200 && nocache == false {
		Daili.Lock.Lock()

		Daili.Data[mainName] = DailiStructData{
			StatusCode:    res.StatusCode,
			ContentType:   ContentType,
			ContentLength: ContentLength,
			Data:          text,
			CreateTime:    time.Now().Unix(),
		}
		Daili.Lock.Unlock()
		return
	}

	return
}

func main() {
	CurMain()
	Daili = DailiStruct{}
	Daili.Data = map[string]DailiStructData{}

	httpClient = &http.Client{
		Timeout: time.Second * 10,
		Transport: &http.Transport{
			MaxIdleConnsPerHost: 1,
			MaxConnsPerHost:     2,
			IdleConnTimeout:     time.Second * 2,
			TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
		},
	}
	fmt.Println("启动成功")
	// 1. 原生http框架
	http.HandleFunc("/", Index)
	err1 := http.ListenAndServe("0.0.0.0:80", nil)
	if err1 != nil {
		fmt.Println(err1)
	}

}
